﻿// author : David Janacek, dj-soft@seznam.cz, 2013-12-13
// company: Asseco Solution, a.s. CZ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.QCode.QrEncoder;
using Noris.Tools.QCode.Common;

namespace Noris.Tools.QCode.QEncoder
{
    /// <summary>
    /// QR code encoder
    /// </summary>
    internal sealed class QEncoder : IDisposable
    {
        #region Internal static řízení encodování. Dispose.
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        internal static QMatrix Encode(string content)
        {
            return _RunEncode(content, ErrorCorrectionLevel.Medium, null);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        /// <returns></returns>
        internal static QMatrix Encode(string content, ErrorCorrectionLevel correctionLevel)
        {
            return _RunEncode(content, correctionLevel, null);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        internal static QMatrix Encode(string content, Int32? useMatrixPattern)
        {
            return _RunEncode(content, ErrorCorrectionLevel.Medium, useMatrixPattern);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        internal static QMatrix Encode(string content, ErrorCorrectionLevel correctionLevel, Int32? useMatrixPattern)
        {
            return _RunEncode(content, correctionLevel, useMatrixPattern);
        }
        /// <summary>
        /// Performs encoding of text (content), 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        internal static QMatrix Encode(IQrMessageFormats message)
        {
            return _RunEncode(message.QrMessage, ErrorCorrectionLevel.Medium, null);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="correctionLevel"></param>
        /// <returns></returns>
        internal static QMatrix Encode(IQrMessageFormats message, ErrorCorrectionLevel correctionLevel)
        {
            return _RunEncode(message.QrMessage, correctionLevel, null);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        internal static QMatrix Encode(IQrMessageFormats message, Int32? useMatrixPattern)
        {
            return _RunEncode(message.QrMessage, ErrorCorrectionLevel.Medium, useMatrixPattern);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="correctionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        internal static QMatrix Encode(IQrMessageFormats message, ErrorCorrectionLevel correctionLevel, Int32? useMatrixPattern)
        {
            return _RunEncode(message.QrMessage, correctionLevel, useMatrixPattern);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        private static QMatrix _RunEncode(string content, ErrorCorrectionLevel correctionLevel, Int32? useMatrixPattern)
        {
            QMatrix matrix = null;
            using (QEncoder qEncoder = new QEncoder())
            {
                matrix = qEncoder._Encode(content, correctionLevel, useMatrixPattern);
            }
            return matrix;
        }
        private QEncoder()
        { }
        void IDisposable.Dispose()
        {
            this._CurrentQErrorCorrector = null;
            this._CurrentQMode = null;
            this._CurrentEciMode = null;
            this._CurrentQCharSet = null;
            this._CurrentQVersion = null;
            if (this._QCode != null)
                ((IDisposable)this._QCode).Dispose();
            this._QCode = null;
            this._DisposeTimeInfo();
        }
        #endregion
        #region Inicializace datových struktur (tabulky verzí, encodingu, error correction, atd)
        /// <summary>
        /// All of QR code versions + their definitions
        /// </summary>
        internal static QVersions Versions { get { if (_Versions == null) _Versions = new QVersions(); return _Versions; } } private static QVersions _Versions;
        /// <summary>
        /// All of QR code modes + their definitions
        /// </summary>
        internal static QModes QModes { get { if (_QModes == null) _QModes = new QModes(); return _QModes; } } private static QModes _QModes;
        /// <summary>
        /// All of QR ErrorCorrectors
        /// </summary>
        internal static QErrorCorrectors QErrorCorrectors { get { if (_QErrorCorrectors == null) _QErrorCorrectors = new QErrorCorrectors(); return _QErrorCorrectors; } } private static QErrorCorrectors _QErrorCorrectors;
        /// <summary>
        /// All of QR CharSet
        /// </summary>
        internal static QCharSets QCharSets { get { if (_QCharSets == null) _QCharSets = new QCharSets(); return _QCharSets; } } private static QCharSets _QCharSets;
        /// <summary>
        /// Graphical patterns
        /// </summary>
        internal static QPatterns QPatterns { get { if (_Patterns == null) _Patterns = new QPatterns(); return _Patterns; } } private static QPatterns _Patterns;
        /// <summary>
        /// ReedSolomon encoder
        /// </summary>
        internal static ReedSolomonEncoder ReedSolomonEncoder { get { if (_ReedSolomonEncoder == null) _ReedSolomonEncoder = new ReedSolomonEncoder(GF256.QR_CODE_FIELD); return _ReedSolomonEncoder; } } private static ReedSolomonEncoder _ReedSolomonEncoder;
        #endregion
        #region Currently active objects
        /// <summary>Currently selected Error Corector</summary>
        private QErrorCorrector _CurrentQErrorCorrector;
        /// <summary>Currently selected mode (NUMERIC / ALPHANUMERIC / BYTE)</summary>
        private QMode _CurrentQMode;
        /// <summary>Currently selected ECI mode, if applicable (or null, when not needed)</summary>
        private QMode _CurrentEciMode;
        /// <summary>Currently selected CharacterSet for encoding extended characters</summary>
        private QCharSet _CurrentQCharSet;
        /// <summary>Currently selected mode (NUMERIC / ALPHANUMERIC / BYTE / ECI)</summary>
        private QVersion _CurrentQVersion;
        /// <summary>Currently created QR code</summary>
        private QCode _QCode;
        #endregion
        #region Encoding mechanism
        /// <summary>
        /// Encode string (content) with ErrorCorrectionLevel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        private QMatrix _Encode(string content, ErrorCorrectionLevel correctionLevel, Int32? useMatrixPattern)
        {
            this._InitTimeInfo();
            this._CurrentQErrorCorrector = this.GetErrorCorrectors(correctionLevel);
            this._CurrentQMode = this.GetMode(content);
            this._AddTimeInfo("Prepare");
            PatternBuffer dataBuffer = this.CreateDataBuffer(content);
            this._AddTimeInfo("CreateData");
            this._CurrentQVersion = this.GetVersion(dataBuffer);
            this._QCode = this.CreateQCode(content);
            this._AddTimeInfo("CreateQCode");
            PatternBuffer headerBuffer = this.CreateHeaderBuffer();
            this.AddLengthInfo(dataBuffer, content, headerBuffer);
            PatternBuffer joinBuffer = headerBuffer + dataBuffer;
            this.TerminateBuffer(joinBuffer);
            this._AddTimeInfo("RawDataFinished");
            PatternBuffer finalBuffer = this.CreateEcInterleavedBuffer(joinBuffer);
            this._AddTimeInfo("EC");
            this.PrepareOptimalMatrix(content, finalBuffer, useMatrixPattern);
            this._AddTimeInfo("Matrixes");
            return this._CreateFinalMatrix();
        }
        /// <summary>
        /// Return instance of QErrorCorrector for specified ErrorCorrectionLevel. (default = Medium)
        /// </summary>
        /// <param name="correctionLevel"></param>
        /// <returns></returns>
        private QErrorCorrector GetErrorCorrectors(ErrorCorrectionLevel correctionLevel)
        {
            QErrorCorrector result; 
            if (!QErrorCorrectors.QErrorDict.TryGetValue(correctionLevel, out result))
                result = QErrorCorrectors.QErrorDict[ ErrorCorrectionLevel.Medium];
            return result;
        }
        /// <summary>
        /// Determine optimal QMode and ECI and Extended QCharSet for given content.
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private QMode GetMode(string content)
        {
            this._CurrentQCharSet = null;
            this._CurrentEciMode = null;

            if (content == null)
                throw new ArgumentNullException("content", "QEncoder.GetMode() can not detect mode for content = NULL.");

            // Beeline: for content = "" is appropriate mode = BYTE:
            if (content.Length == 0)
                return QModes.QModeDict[QModeType.Byte];

            // List all modes, which can encode data; in order from biggest to smallest code:
            List<QMode> modes = new List<QMode>() { QEncoder.QModes.QModeDict[QModeType.Byte], QEncoder.QModes.QModeDict[QModeType.Alphanumeric], QEncoder.QModes.QModeDict[QModeType.Numeric] };
            List<QCharSet> charSets = QEncoder.QCharSets.GetCharSetList();        // All charsets for handling characters 0xA0 ÷ 0xFF
            bool containECI = false;
            foreach (char c in content)
            {
                if (((int)c) < 0x80)
                {   // Standard ASCII = 0x00 ÷ 0x7F:
                    modes.RemoveAll(mode => !mode.CanEncode(c));     // remove all modes, which can not encode given char
                }
                else
	            {   // Extended ASCII:
                    containECI = true;
                    charSets.RemoveAll(set => !set.CanEncode(c));    // remove all CharSets, which can not encode given char
	            }

                if (modes.Count == 0 && charSets.Count == 0)
                    throw new ArgumentOutOfRangeException("content", "Code contain characters, which can not be encoded with any one Mode and/or CharacterSet. Content: \"" + content + "\".");
            }
            
            if (containECI)
            {
                if (charSets.Count == 0)
                    throw new ArgumentOutOfRangeException("content", "Code contain characters, which can not be encoded with any one CharacterSet. Content: \"" + content + "\".");
                this._CurrentEciMode = QEncoder.QModes.QModeDict[QModeType.Eci];
                this._CurrentQCharSet = charSets[0];
                return QEncoder.QModes.QModeDict[QModeType.Byte];
            }
            if (modes.Count == 0)
                throw new ArgumentOutOfRangeException("content", "Code contain characters, which can not be encoded with any one Mode. Content: \"" + content + "\".");
            return  modes[modes.Count - 1];                          // result = mode, which produce smallest code (but if content contain ECI, then mode = BYTE)
        }
        /// <summary>
        /// Encode (content) using this._CurrentQMode and return buffer
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private PatternBuffer CreateDataBuffer(string content)
        {
            PatternBuffer buffer = new PatternBuffer();
            this._CurrentQMode.EncodeDataToBuffer(content, this._CurrentQCharSet, buffer);
            return buffer;
        }
        /// <summary>
        /// Select and return QR version, with smallest version number, which can store specified data buffer (+3 extra bytes).
        /// </summary>
        /// <param name="dataBuffer"></param>
        /// <returns></returns>
        private QVersion GetVersion(PatternBuffer dataBuffer)
        {
            int requiredDataBytes = dataBuffer.SizeInByte + 3;            // Size of data + 3 extra bytes for (the header = mode info and length info)
            ErrorCorrectionLevel ecLevel = this._CurrentQErrorCorrector.Level;
            foreach (QVersion version in Versions.VersionList)
            {
                int numDataBytes = version.GetDataByteCountForEcLevel(ecLevel);
                if (numDataBytes >= requiredDataBytes)
                    return version;
            }
            throw new ArgumentOutOfRangeException("dataBuffer", "Content for QR code is too big to encode. Buffer length=" + dataBuffer.SizeInByte.ToString() + " Byte.");
        }
        /// <summary>
        /// Create new QCode and pre-fill it for current data
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private QCode CreateQCode(string content)
        {
            QCode qCode = new QCode(content, this._CurrentQVersion, this._CurrentQErrorCorrector, this._CurrentQMode);


            return qCode;
        }
        /// <summary>
        /// Create a buffer with header information (charset, mode).
        /// </summary>
        /// <returns></returns>
        private PatternBuffer CreateHeaderBuffer()
        {
            PatternBuffer buffer = new PatternBuffer();

            if (this._CurrentQMode.Mode == QModeType.Byte && this._CurrentQCharSet != null && this._CurrentEciMode != null)
            {
                this._CurrentEciMode.EncodeHeaderToBuffer(buffer);         // Signature ECI = "0111"
                this._CurrentQCharSet.EncodeHeaderToBuffer(buffer);        // ECI Assignment number = 
            }
            
            this._CurrentQMode.EncodeHeaderToBuffer(buffer);               // Signature selected mode, i.e. for mode BYTE = "0100"
            
            return buffer;
        }
        /// <summary>
        /// Create length info for current data (data buffer or content, by mode), and append it to header buffer.
        /// </summary>
        /// <param name="dataBuffer"></param>
        /// <param name="content"></param>
        /// <param name="qMode"></param>
        /// <param name="headerBuffer"></param>
        private void AddLengthInfo(PatternBuffer dataBuffer, string content, PatternBuffer headerBuffer)
        {
            QMode qMode = this._CurrentQMode;
            int numLetters = (qMode.Mode == QModeType.Byte ? dataBuffer.SizeInByte : content.Length);
            int bitCount = qMode.GetCharacterCountBitsForVersion(this._CurrentQVersion);
            string pattern = PatternBuffer.CreatePattern(numLetters, bitCount, "QVersion.AddLengthInfo(): current number of letters (=" + numLetters + ") is too big to fit into " + bitCount.ToString() + " bits.");
            headerBuffer.Append(pattern);
        }
        /// <summary>
        /// Insert end patterns to buffer (terminator zero, aligning to Byte, fill dummy patterns)
        /// </summary>
        /// <param name="buffer"></param>
        private void TerminateBuffer(PatternBuffer buffer)
        {
            int bufferSize = buffer.Size;
            int numDataBytes = this._QCode.NumDataBytes;
            int numDataBits = numDataBytes * 8;
            int numTermBits = numDataBits - bufferSize;
            if (numTermBits < 0)
                throw new InvalidOperationException("Size of buffer (" + bufferSize.ToString() + " bits) exceeding capacity of current version #" + this._CurrentQVersion.VersionNumber.ToString() + " (" + numDataBits.ToString() + " bits).");
            if (numTermBits == 0) return;

            // 4 termination bits "0", not exceeding capacity of version (See 8.4.8 of JISX0510:2004 (p.24) for details.):
            int termCount = (numTermBits < 4 ? numTermBits : 4);
            buffer.Append(0, termCount);
            numTermBits -= termCount;
            if (numTermBits == 0) return;

            // Align with "0" to whole byte:
            int alignCount = buffer.NotAlignedBiteCount;
            if (alignCount > 0)
            {
                buffer.Append(0, alignCount);
                numTermBits -= alignCount;
                if (numTermBits == 0) return;
            }
            if (buffer.NotAlignedBiteCount != 0)
                throw new InvalidOperationException("Aligning buffer to whole Byte failed, NotAlignedBiteCount=" + buffer.NotAlignedBiteCount.ToString() + " bits, after aligning.");

            // Fill remaining space (to numDataBytes) with values 0xEC and 0x11 (padding patterns defined in 8.4.9 (p.24)):
            bool alt = true;
            while (buffer.SizeInByte < numDataBytes)
            {
                buffer.Append((alt ? 0xEC : 0x11), 8);      // Alternate 8bit patterns
                alt = !alt;
            }
            if (buffer.Size != numDataBits)
                throw new InvalidOperationException("Terminate buffer failed, buffer.Size=" + buffer.Size.ToString() + " bits, reqiured size=" + numDataBits.ToString() + " bits.");
        }
        /// <summary>
        /// Apply selected Error-Correction mode (with using Reed-Solomon algorithm) to buffer, and return new buffer containing data and EC blocks.
        /// See 8.6 of JISX0510:2004 (p.37) for details.
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        private PatternBuffer CreateEcInterleavedBuffer(PatternBuffer buffer)
        {
            // CZ princip:
            //  pracuje se na úrovni Byte (=8bitů vstupního bufferu);
            //  vstupní buffer postupně rozřežu na bloky (1 prvek bloku = int = obsahuje 8 bitů bufferu), délka bloku odpovídá aktuální deklarace (this._QCode.QEcBlockSet),
            //  ke každému bloku dat vygenerujeme odpovídající blok EC.
            //  Následně skládáme výstupní buffer v rozházeném pořadí: za sebe dáváme hodnoty nejprve z pozice [0] ze všech bloků, dále z pozice [1] ze všech bloků, atd.
            //  Dojde tedy k ukládání křížem: nejprve buffer rozřežeme na řádky, položíme nad sebe, a pak do výsledku dáváme ze sloupců.
            //  Obdobným mechanismem poté přidáme bloky EC.
            // Info: See 8.6 of JISX0510:2004 (p.37) for details.

            List<QRsBlock> blocks = this._PrepareQRsBlocks(buffer);

            PatternBuffer result = new PatternBuffer();
            this._EncodeInterleavedToBuffer(blocks, this._QCode.RsBlockDataBytesMax, b => b.Data, result);    // Apend Data bytes
            this._EncodeInterleavedToBuffer(blocks, this._QCode.RsBlockEcBytesMax, b => b.Ec, result);        // Apend EC bytes

            return result;
        }
        /// <summary>
        /// Cut data buffer into List of QRsBlock, find appropriate QCodeRsBlockData for each block, calculate ReedSolomon correction.
        /// </summary>
        /// <returns></returns>
        private List<QRsBlock> _PrepareQRsBlocks(PatternBuffer buffer)
        {
            int bufferSizeByte = buffer.SizeInByte;
            int pointer = 0;
            List<QRsBlock> blocks = new List<QRsBlock>();
            for (int rsBlock = 0; rsBlock < this._QCode.NumRSBlocks; rsBlock++)
            {
                QCodeRsBlockData rsData = this._QCode.GetRsBlockInfo(rsBlock);

                // Data from buffer to int[]:
                int dataLength = rsData.BytesInBlockData;
                QRsBlock block = new QRsBlock(rsBlock, pointer, rsData);
                block.Data = buffer.ToByteArray(pointer, dataLength);

                // ReedSolomon correction:
                int ecLen = rsData.BytesInBlockEC;
                int totalLength = dataLength + ecLen;
                int[] toEncode = new int[totalLength];
                Array.Copy(block.Data, 0, toEncode, 0, dataLength);

                ReedSolomonEncoder encoder = ReedSolomonEncoder;
                if (encoder == null)
                    encoder = new ReedSolomonEncoder(GF256.QR_CODE_FIELD);

                encoder.encode(toEncode, ecLen);

                int[] ec = new int[ecLen];
                Array.Copy(toEncode, dataLength, ec, 0, ecLen);
                block.Ec = ec;

                // One block to result:
                blocks.Add(block);
                pointer += dataLength;
            }

            return blocks;
        }
        /// <summary>
        /// Encode values from blocks, with interleaving to buffer
        /// </summary>
        /// <param name="blocks"></param>
        /// <param name="maxLength"></param>
        /// <param name="dataSelector"></param>
        /// <param name="buffer"></param>
        private void _EncodeInterleavedToBuffer(List<QRsBlock> blocks, int maxLength, Func<QRsBlock, int[]> dataSelector, PatternBuffer buffer)
        {
            for (int i = 0; i < maxLength; ++i)
                foreach (QRsBlock block in blocks)
                {
                    int[] array = dataSelector(block);                 // Selector returns block.Data or block.Ec (see caller)
                    if (i < array.Length)
                        buffer.Append(array[i], 8);
                }
        }
        /// <summary>
        /// Create matrix for data from buffer into this._QCode.Matrix.
        /// Calculate matrix for all disponible mask, calculate penalty for each from them, and select best one.
        /// </summary>
        /// <param name="buffer"></param>
        private void PrepareOptimalMatrix(string content, PatternBuffer buffer, Int32? useMatrixPattern)
        {
            List<QMatrixInfo> matrixes = new List<QMatrixInfo>();
            if (useMatrixPattern.HasValue)
            {
                int pattern = useMatrixPattern.Value;
                QMatrixInfo matrixInfo = this._CreateMatrixInfo(content, pattern, buffer);
                matrixes.Add(matrixInfo);
            }
            else
            {
                for (int pattern = 0; pattern < 8; pattern++)
                {
                    QMatrixInfo matrixInfo = this._CreateMatrixInfo(content, pattern, buffer);
                    matrixes.Add(matrixInfo);
                }
                matrixes.Sort((a, b) => a.Penalty.CompareTo(b.Penalty));
            }
            this._QCode.MatrixInfo = matrixes[0];
        }
        /// <summary>
        /// Create a final shape of matrix.
        /// All working matrixes are tight-size (does not have quiet zone).
        /// Here will be created new matrix with this zone (empty frame).
        /// </summary>
        /// <returns></returns>
        private QMatrix _CreateFinalMatrix()
        {
            QVersion version = this._CurrentQVersion;
            QMatrix matrix = this._QCode.Matrix;
            int quiet = version.QuietZone;
            QMatrix result = new QMatrix(matrix.Width + 2 * quiet, matrix.Height + 2 * quiet);
            result.Set(quiet, quiet, matrix, true);
            return result;
        }
        #endregion
        #region Fill matrix
        /// <summary>
        /// Create, fill and return Matrix with data.
        /// </summary>
        /// <param name="pattern"></param>
        /// <param name="buffer"></param>
        /// <returns></returns>
        private QMatrixInfo _CreateMatrixInfo(string content, int pattern, PatternBuffer buffer)
        {
            QMatrixInfo matrixInfo = new QMatrixInfo(pattern, this._QCode.MatrixWidth);
            this._FillMatrixDescriptions(matrixInfo, content);
            this._FillMatrixBasic(matrixInfo);
            this._FillMatrixType(matrixInfo);
            this._FillMatrixVersion(matrixInfo);
            this._AddTimeInfo("Matrix #" + pattern.ToString() + " Fill Formats");
            this._FillMatrixData(matrixInfo, buffer);
            this._AddTimeInfo("Matrix #" + pattern.ToString() + " Fill Data");
            this._CalculatePenalty(matrixInfo);
            this._AddTimeInfo("Matrix #" + pattern.ToString() + " Penalized");
            return matrixInfo;
        }
        /// <summary>
        /// Fill descriptions into QMatrix (Content, CodeType, CodeVersion)
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _FillMatrixDescriptions(QMatrixInfo matrixInfo, string content)
        {
            matrixInfo.Matrix.Content = content;
            matrixInfo.Matrix.CodeType = "QR code";
            matrixInfo.Matrix.CodeVersion = this._CurrentQVersion.VersionNumber.ToString() + "." + matrixInfo.MatrixPattern.ToString();
        }
        /// <summary>
        /// Fill basic pattern (Detection, Separators, OnePoint, Adjustment, Timing)
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _FillMatrixBasic(QMatrixInfo matrixInfo)
        {
            QMatrix matrix = matrixInfo.Matrix;
            QVersion version = this._CurrentQVersion;
            QPattern pattern;

            // Detection Squares:
            pattern = QPatterns.PatternDict[QPatternType.PositionDetection];
            foreach (Coord c in version.MatrixDetectionCenterCoords)
                matrix.Set(c.X - 3, c.Y - 3, pattern.Matrix);

            // Horizontal separators:
            pattern = QPatterns.PatternDict[QPatternType.HorizontalSeparation];
            foreach (Coord c in version.MatrixHorizontalSeparatorCoords)
                matrix.Set(c, pattern.Matrix);

            // Vertical separators:
            pattern = QPatterns.PatternDict[QPatternType.VerticalSeparation];
            foreach (Coord c in version.MatrixVerticalSeparatorCoords)
                matrix.Set(c, pattern.Matrix);

            // Black point:
            matrix[version.MatrixLeftBottomPointCoord] = '1';

            // Adjustment Squares:
            pattern = QPatterns.PatternDict[QPatternType.PositionAdjustment];
            foreach (Coord c in version.MatrixAdjustmentCenterCoords)
            {
                if (matrix.IsEmptyPoint(c.X, c.Y))
                    matrix.Set(c.X - 2, c.Y - 2, pattern.Matrix);
            }

            // Timing (Horizontal, Vertical):
            matrix.Set(version.MatrixHorizontalTimingCoord, version.MatrixHorizontalTimingMatrix);
            matrix.Set(version.MatrixVerticalTimingCoord, version.MatrixVerticalTimingMatrix);
        }
        /// <summary>
        /// Fill EC mode and MatrixPattern into matrix
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _FillMatrixType(QMatrixInfo matrixInfo)
        {
            QVersion version = this._CurrentQVersion;
            QMatrix matrix = matrixInfo.Matrix;

            PatternBuffer buffer = this._CreateEcPatternBuffer(matrixInfo.MatrixPattern);
            for (int i = 0; i < 15; i++)
            {
                matrix[version.MatrixTypeInfoSet1Coords[i]] = buffer[14 - i];
                matrix[version.MatrixTypeInfoSet2Coords[i]] = buffer[14 - i];
            }
        }
        /// <summary>
        /// Create pattern containing EC mode and MatrixPattern
        /// </summary>
        /// <param name="matrixPattern"></param>
        /// <returns></returns>
        private PatternBuffer _CreateEcPatternBuffer(int matrixPattern)
        {
            PatternBuffer buffer = new PatternBuffer();
            int ecmx = ((this._CurrentQErrorCorrector.Bits << 3) | (matrixPattern & 0x07));
            buffer.Append(ecmx, 5);
            int bchc = _CalculateBCHCode(ecmx, TYPE_INFO_POLY);
            buffer.Append(bchc, 10);
            if (buffer.Size != 15)
                throw new InvalidProgramException("CreateEcPatternBuffer() produce buffer with Size != 15.");

            PatternBuffer maskBuffer = new PatternBuffer();
            maskBuffer.Append(TYPE_INFO_MASK_PATTERN, 15);
            if (maskBuffer.Size != 15)
                throw new InvalidProgramException("CreateEcPatternBuffer() produce maskBuffer with Size != 15.");

            return buffer ^ maskBuffer;
        }
        /// <summary>
        /// Fill QR code version into matrix
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _FillMatrixVersion(QMatrixInfo matrixInfo)
        {
            QVersion version = this._CurrentQVersion;
            if (!version.IsVersionWithVersionInfo) return;        // For version 1÷7 dont need version info

            this._PrepareMatrixVersionInfo();

            QMatrix matrix = matrixInfo.Matrix;
            matrix.Set(version.MatrixVersionInfoSet1Coord, version.MatrixVersionInfoSet1);
            matrix.Set(version.MatrixVersionInfoSet2Coord, version.MatrixVersionInfoSet2);
        }
        /// <summary>
        /// Prepare pattern containing Version number, into this._CurrentQVersion.
        /// </summary>
        /// <returns></returns>
        private void _PrepareMatrixVersionInfo()
        {
            QVersion version = this._CurrentQVersion;
            if (!version.IsPreparedVersionInfo)
            {   // Matrixes are not prepared yet, prepare them now:
                // Matrixes are constants for each version.
                PatternBuffer buffer = new PatternBuffer();
                buffer.Append(version.VersionNumber, 6);
                int bchCode = _CalculateBCHCode(version.VersionNumber, VERSION_INFO_POLY);
                buffer.Append(bchCode, 12);
                version.PrepareVersionInfo(buffer.BufferString);
            }
        }
        /// <summary>
        /// Fill data from prepared buffer into matrix
        /// </summary>
        /// <param name="matrixInfo"></param>
        /// <param name="buffer"></param>
        private void _FillMatrixData(QMatrixInfo matrixInfo, PatternBuffer buffer)
        {
            QMatrix matrix = matrixInfo.Matrix;
            int bitIndex = 0;
            int bitCount = buffer.Size;
            int dir = -1;
            int x = matrix.Width - 1;
            int y = matrix.Height - 1;

            while (x > 0)
            {
                if (x == 6) x--;                 // Skip the vertical timing pattern.
                while (y >= 0 && y < matrix.Height)
                {
                    for (int i = 0; i < 2; ++i)
                        this._FillMatrixDataOne(matrixInfo, buffer, ref bitIndex, x - i, y);
                    y += dir;
                }
                dir = -dir;                      // Reverse the direction.
                y += dir;
                x -= 2;                          // Move to the left.
            }

            if (bitIndex != buffer.Size)
                throw new InvalidProgramException("QEncoder._FillMatrixData(): Not all bits of QR code was written to matrix, remaining: " + (buffer.Size - bitIndex).ToString() + " bits.");
        }
        /// <summary>
        /// If cell [x,y] of matrix is Empty, then store in this cell a value from data[bitIndex], if there is any (if not, then fill remainig cells with 0).
        /// Apply MaskPattern.
        /// </summary>
        /// <param name="matrixInfo"></param>
        /// <param name="buffer"></param>
        /// <param name="bitIndex"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void _FillMatrixDataOne(QMatrixInfo matrixInfo, PatternBuffer buffer, ref int bitIndex, int x, int y)
        {
            if (matrixInfo.Matrix[x, y] != QMatrix.Empty) return;

            char bit;
            if (bitIndex < buffer.Size)
            {
                bit = buffer[bitIndex];          // Data bit
                ++bitIndex;
            }
            else
            {
                bit = '0';                       // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described in 8.4.9 of JISX0510:2004 (p. 24).
            }

            // Mask bit:
            if (matrixInfo.MatrixPattern != -1 && IsDataBitMasked(matrixInfo.MatrixPattern, x, y))
                bit = (bit == '0' ? '1' : '0');  // XOR bit

            // Write value into matrix:
            matrixInfo.Matrix[x, y] = bit;
        }
        /// <summary>
        /// return true, if bit on point [x,y] in matrixPattern has be XOR-ed.
        /// </summary>
        /// <param name="matrixPattern"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        internal static bool IsDataBitMasked(int matrixPattern, int x, int y)
        {
            int r, w;
            switch (matrixPattern)
            {

                case 0:
                    r = (y + x) & 0x1;
                    break;

                case 1:
                    r = y & 0x1;
                    break;

                case 2:
                    r = x % 3;
                    break;

                case 3:
                    r = (y + x) % 3;
                    break;

                case 4:
                    r = ((y >> 1) + (x / 3)) & 0x1;
                    break;

                case 5:
                    w = y * x;
                    r = (w & 0x1) + (w % 3);
                    break;

                case 6:
                    w = y * x;
                    r = (((w & 0x1) + (w % 3)) & 0x1);
                    break;

                case 7:
                    w = y * x;
                    r = (((w % 3) + ((y + x) & 0x1)) & 0x1);
                    break;

                default:
                    throw new ArgumentException("Invalid mask pattern: " + matrixPattern, "matrixPattern");

            }
            return (r == 0);
        }
        #region Bose-Chaudhuri-Hocquenghem code
        /// <summary>
        /// Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". 
        /// The BCH code is used for encoding type information and version information.
        /// Example: Calculation of version information of 7.
        /// f(x) is created from 7.
        ///   - 7 = 000111 in 6 bits
        ///   - f(x) = x^2 + x^2 + x^1
        /// g(x) is given by the standard (p. 67)
        ///   - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1
        /// Multiply f(x) by x^(18 - 6)
        ///   - f'(x) = f(x) * x^(18 - 6)
        ///   - f'(x) = x^14 + x^13 + x^12
        /// Calculate the remainder of f'(x) / g(x)
        ///         x^2
        ///         __________________________________________________
        ///   g(x) )x^14 + x^13 + x^12
        ///         x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2
        ///         --------------------------------------------------
        ///                              x^11 + x^10 + x^7 + x^4 + x^2
        ///
        /// The remainder is x^11 + x^10 + x^7 + x^4 + x^2
        /// Encode it in binary: 110010010100
        /// The return value is 0xc94 (1100 1001 0100)
        ///
        /// Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit
        /// operations. We don't care if cofficients are positive or negative.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="polynom"></param>
        /// <returns></returns>
        private static int _CalculateBCHCode(int value, int polynom)
        {
            int v = value;
            // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1
            // from 13 to make it 12.
            int msbSetInPoly = _FindMSBBit(polynom);
            v <<= msbSetInPoly - 1;
            // Do the division business using exclusive-or operations.
            while (_FindMSBBit(v) >= msbSetInPoly)
            {
                v ^= polynom << (_FindMSBBit(v) - msbSetInPoly);
            }
            // Now the "value" is the remainder (i.e. the BCH code)
            return v;
        }
        /// <summary>
        /// Return the position of the most significant bit set (to one) in the "value". The most
        /// significant bit is position 32. If there is no bit set, return 0. Examples:
        /// - findMSBSet(0) => 0
        /// - findMSBSet(1) => 1
        /// - findMSBSet(255) => 8
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static int _FindMSBBit(int value)
        {
            int msb = 0;
            int v = value;
            while (v != 0)
            {
                v = v >> 1;
                ++msb;
            }
            return msb;
        }
        #endregion

        /// <summary>0x0537 = From Appendix C in JISX0510:2004 (p.65)</summary>
        private const int TYPE_INFO_POLY = 0x537;
        /// <summary>0x1F25 = From Appendix D in JISX0510:2004 (p.67)</summary>
        private const int VERSION_INFO_POLY = 0x1f25;
        /// <summary>0x5412 = From Appendix   in JISX0510:2004 (p.  )</summary>
        private const int TYPE_INFO_MASK_PATTERN = 0x5412;

        #endregion
        #region Check matrix, compute matrix penalty
        /// <summary>
        /// Calculate penalty for a matrix (by 5 rules), and result store into QMatrixInfo.
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _CalculatePenalty(QMatrixInfo matrixInfo)
        {
            QMatrix matrix = matrixInfo.Matrix;
            int penalty = 0;
            int blackPoints = 0;           // Little hack for time-optimizing: in Rule1 we calcultae sum(dark points) into out param darkPoints.
            int p1 = this._CalculatePenalty1(matrix, QMatrix.ScanCellsOrder.Rows, out blackPoints);
            int p2 = this._CalculatePenalty1(matrix, QMatrix.ScanCellsOrder.Columns, out blackPoints);
            int p5 = this._CalculatePenalty4(matrix, blackPoints);
            penalty += (p1 + p2 + p5);
            matrix.ForEach(
                new Action<int, int>((x, y) => 
                    {
                        
                        int p3 = _CalculatePenalty2(matrix, x, y);        // squares 2x2
                        int p4 = _CalculatePenalty3(matrix, x, y);        // sequences 00001011101, 10111010000, 000010111010000
                        penalty += (p3 + p4 + p5);
                    }));
            matrixInfo.Penalty = penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 1 (for Rows) and 2 (for Columns):
        /// add penalty 3 for each 5 same point in line, add penalty 1 for each next (after 5) same point.
        /// </summary>
        /// <param name="matrix"></param>
        /// <param name="scanOrder"></param>
        /// <returns></returns>
        private int _CalculatePenalty1(QMatrix matrix, Common.QMatrix.ScanCellsOrder scanOrder, out int blackPoints)
        {
            int penalty = 0;
            int dpCount = 0;                     // Black point count. We can not use directly "out parameter" in lambda method.
            char empty = QMatrix.Empty;
            char last = empty;
            int steadyCount = 0;                 // Number of consecutive cells with identical value
            matrix.ForEach(QMatrix.ScanCellsOrder.Rows,
                new Action<int, int>((x, y) =>
                {   // Action is called for each cell, in order by row or column, by order:
                    char curr = matrix[x, y];    // Value in cell
                    if (last != empty && curr == last)
                    {   // Still the same:
                        steadyCount++;
                        if (steadyCount == 5)
                            penalty += 3;        // At fifth same point penalty +3
                        else if (steadyCount > 5)
                            penalty++;           // At next same point (after fifth) penalty +1
                    }
                    else
                    {   // Change => new pattern:
                        last = curr;
                        steadyCount = 1;
                    }
                    if (curr == '1')
                        dpCount++;
                }
                ),
                new Action<int>(g =>
                {   // Action is called after scan whole group (row or column, by order)
                    //  => reset for new group:
                    last = empty;
                    steadyCount = 0;
                }
                ));

            blackPoints = dpCount;
            return penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 2:
        /// add penalty 3 for each square 2x2 point with same value.
        /// </summary>
        /// <param name="matrix"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private int _CalculatePenalty2(QMatrix matrix, int x, int y)
        {
            if (x < matrix.Width - 2 && y < matrix.Height - 2)
            {
                char c = matrix[x, y];
                if (matrix[x + 1, y] == c && matrix[x, y + 1] == c && matrix[x + 1, y + 1] == c)
                    return 3;
            }
            return 0;
        }
        /// <summary>
        /// Calculate penalty by rule 3:
        /// add penalty 40 for each sequence "00001011101" or "10111010000" (length = 11)
        /// add penalty 80 for each sequence "000010111010000" (length = 15).
        /// </summary>
        /// <param name="matrix"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private int _CalculatePenalty3(QMatrix matrix, int x, int y)
        {
            int penalty = 0;
            // Note: any changes lead to a significant slowdown !!!
            // This code is called repeatedly for each cell in matrix !!!

            // in X dimesnion, when x < (width - 11): "00001011101" or "000010111010000":
            int w = matrix.Width;
            if (x < (w - 11))
            {   // Sequence is "00001011101" ?
                if (matrix[x + 00, y] == '0' &&
                    matrix[x + 01, y] == '0' &&
                    matrix[x + 02, y] == '0' &&
                    matrix[x + 03, y] == '0' &&
                    matrix[x + 04, y] == '1' &&
                    matrix[x + 05, y] == '0' &&
                    matrix[x + 06, y] == '1' &&
                    matrix[x + 07, y] == '1' &&
                    matrix[x + 08, y] == '1' &&
                    matrix[x + 09, y] == '0' &&
                    matrix[x + 10, y] == '1')
                {
                    penalty += 40;
                    // if current data begins with code "00001011101", and if after are at least 4 next cells, then test it for code "0000",
                    // this can add penalty +40 (summary code = "00001011101" + "0000"):
                    if (x < (w - 15) &&
                        matrix[x + 11, y] == '0' &&
                        matrix[x + 12, y] == '0' &&
                        matrix[x + 13, y] == '0' &&
                        matrix[x + 14, y] == '0')
                        penalty += 40;
                }

                // Sequence is "10111010000" ?
                if (matrix[x + 00, y] == '1' &&
                    matrix[x + 01, y] == '0' &&
                    matrix[x + 02, y] == '1' &&
                    matrix[x + 03, y] == '1' &&
                    matrix[x + 04, y] == '1' &&
                    matrix[x + 05, y] == '0' &&
                    matrix[x + 06, y] == '1' &&
                    matrix[x + 07, y] == '0' &&
                    matrix[x + 08, y] == '0' &&
                    matrix[x + 09, y] == '0' &&
                    matrix[x + 10, y] == '0')
                {
                    penalty += 40;
                }
            }

            // same in Y dimesnion:
            int h = matrix.Height;
            if (y < (h - 11))
            {   // "00001011101" ?
                if (matrix[x, y + 00] == '0' &&
                    matrix[x, y + 01] == '0' &&
                    matrix[x, y + 02] == '0' &&
                    matrix[x, y + 03] == '0' &&
                    matrix[x, y + 04] == '1' &&
                    matrix[x, y + 05] == '0' &&
                    matrix[x, y + 06] == '1' &&
                    matrix[x, y + 07] == '1' &&
                    matrix[x, y + 08] == '1' &&
                    matrix[x, y + 09] == '0' &&
                    matrix[x, y + 10] == '1')
                {
                    penalty += 40;
                    // "000010111010000" ?
                    if (y < (h - 15) &&
                        matrix[x, y + 11] == '0' &&
                        matrix[x, y + 12] == '0' &&
                        matrix[x, y + 13] == '0' &&
                        matrix[x, y + 14] == '0')
                        penalty += 40;
                }

                // "10111010000" ?
                if (matrix[x, y + 00] == '1' &&
                    matrix[x, y + 01] == '0' &&
                    matrix[x, y + 02] == '1' &&
                    matrix[x, y + 03] == '1' &&
                    matrix[x, y + 04] == '1' &&
                    matrix[x, y + 05] == '0' &&
                    matrix[x, y + 06] == '1' &&
                    matrix[x, y + 07] == '0' &&
                    matrix[x, y + 08] == '0' &&
                    matrix[x, y + 09] == '0' &&
                    matrix[x, y + 10] == '0')
                {
                    penalty += 40;
                }
            }
            return penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 4:
        /// Calculate the ratio of dark cells and give penalty if the ratio is far from 50%.
        /// It gives 10 penalty for 5% distance.
        /// </summary>
        /// <param name="matrix"></param>
        /// <param name="blackPoints"></param>
        /// <returns></returns>
        private int _CalculatePenalty4(QMatrix matrix, int blackPoints)
        {
            decimal ratio = (decimal)blackPoints / (decimal)(matrix.Width * matrix.Height); // 0.00 = all is '0',  1.00 = all is '1'. Optimal = 0.50.
            int penalty = (int)(200m * Math.Abs(ratio - 0.5m));      // 0.50 => 0; 0.00 => 100; 1.00 => 100
            return penalty;
        }
        #region Check matrix, compute matrix penalty - old version, 2 milisec per 1 matrix of type 4
        /// <summary>
        /// Calculate penalty for a matrix (by 5 rules), and result store into QMatrixInfo.
        /// </summary>
        /// <param name="matrixInfo"></param>
        private void _CalculatePenaltyOld(QMatrixInfo matrixInfo)
        {
            QMatrix matrix = matrixInfo.Matrix;
            int penalty = 0;
            penalty += this._CalculatePenaltyByRule1(matrix);
            penalty += this._CalculatePenaltyByRule1(matrix.CreateTransform(QMatrixTransformMode.SwapXY));    // Rule 1 in perpendicular direction
            penalty += this._CalculatePenaltyByRule2(matrix);
            penalty += this._CalculatePenaltyByRule3(matrix);
            penalty += this._CalculatePenaltyByRule4(matrix);
            matrixInfo.Penalty = penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 1:
        /// add penalty 3 for each 5 same point in line, add penalty 1 for each next (after 5) same point.
        /// </summary>
        /// <param name="matrix"></param>
        /// <returns></returns>
        private int _CalculatePenaltyByRule1(QMatrix matrix)
        {
            int penalty = 0;
            char empty = QMatrix.Empty;
            char last = empty;
            int steadyCount = 0;                 // Number of consecutive cells with identical value
            matrix.ForEach(
                new Action<int, int>((x, y) => 
                {   // Action is called for each cell, in order X++; Y++ :
                    char curr = matrix[x, y];    // Value in cell
                    if (last != empty && curr == last)
                    {   // Still the same:
                        steadyCount++;
                        if (steadyCount == 5)
                            penalty += 3;        // At fifth same point penalty +3
                        else if (steadyCount > 5)
                            penalty++;           // At next same point (after fifth) penalty +1
                    }
                    else
                    {   // Change => new pattern:
                        last = curr;
                        steadyCount = 1;
                    }
                }
                ),
                new Action<int>(y =>
                {   // Action is called after scan whole row (all dimension X on one dimension Y):
                    //  => reset for new row:
                    last = empty;
                    steadyCount = 0;
                }
                ));

            return penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 2:
        /// add penalty 3 for each square 2x2 point with same value.
        /// </summary>
        /// <param name="matrix"></param>
        /// <returns></returns>
        private int _CalculatePenaltyByRule2(QMatrix matrix)
        {
            int penalty = 0;
            int lastX = matrix.Width - 2;
            int lastY = matrix.Height - 2;
            matrix.ForEach(
                new Action<int, int>((x, y) =>
                {   // Action is called for each cell, in order X++; Y++ :
                    if (x <= lastX && y <= lastY)
                    {   // Exclude last row or last column:
                        string square = matrix.Extract(x, y, 2, 2, ""); // Store square 2x2 at coord [x,y]÷[x+1,y+1] into string without line separation
                        if (square == "".PadRight(4, matrix[x, y]))     // If square == string of 4 same char from cell [x,y] (i.e. "1111"):
                            penalty += 3;
                    }
                }
                ));
            return penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 3:
        /// add penalty 40 for each sequence "00001011101" or "10111010000" (length = 11)
        /// add penalty 80 for each sequence "000010111010000" (length = 15).
        /// </summary>
        /// <param name="matrix"></param>
        /// <returns></returns>
        private int _CalculatePenaltyByRule3(QMatrix matrix)
        {
            string debug = "";

            int penalty = 0;
            string sample1a = "00001011101";
            string sample1b = "10111010000";
            string sample2 = "000010111010000";
            int length1 = sample1a.Length;
            int length2 = sample2.Length;
            int lastX1 = matrix.Width - length1;      // Last coordinate X for test for sequence 1a + 1b
            int lastX2 = matrix.Width - length2;      // Last coordinate X for test for sequence 2
            int lastY1 = matrix.Height - length1;     // Last coordinate Y for test for sequence 1a + 1b
            int lastY2 = matrix.Height - length2;     // Last coordinate Y for test for sequence 2
            matrix.ForEach(
                new Action<int, int>((x, y) =>
                {   // Action is called for each cell, in order X++; Y++ :
                    
                    // Horizontal:
                    if (x <= lastX1)
                    {   // test for sequence 1:
                        string sequence = matrix.Extract(x, y, length1, 1, ""); // Store horizontal sequence of 11 char into string
                        if (sequence == sample1a || sequence == sample1b)
                        {
                            penalty += 40;
                            debug += "[" + x.ToString() + ", " + y.ToString() + "] = H1: " + sequence + Environment.NewLine;
                        }
                    }
                    if (x <= lastX2)
                    {   // test for sequence 2:
                        string sequence = matrix.Extract(x, y, length2, 1, ""); // Store horizontal sequence of 15 char into string
                        if (sequence == sample2)
                        {
                            penalty += 40;
                            debug += "[" + x.ToString() + ", " + y.ToString() + "] = H2: " + sequence + Environment.NewLine;
                        }
                    }

                    // Vertical:
                    if (y <= lastY1)
                    {   // test for sequence 1:
                        string sequence = matrix.Extract(x, y, 1, length1, ""); // Store vertical sequence of 11 char into string
                        if (sequence == sample1a || sequence == sample1b)
                        {
                            penalty += 40;
                            debug += "[" + x.ToString() + ", " + y.ToString() + "] = V1: " + sequence + Environment.NewLine;
                        }
                    }
                    if (y <= lastY2)
                    {   // test for sequence 2:
                        string sequence = matrix.Extract(x, y, 1, length2, ""); // Store vertical sequence of 15 char into string
                        if (sequence == sample2)
                        {
                            penalty += 40;
                            debug += "[" + x.ToString() + ", " + y.ToString() + "] = V2: " + sequence + Environment.NewLine;
                        }
                    }
                }
                ));
            return penalty;
        }
        /// <summary>
        /// Calculate penalty by rule 4:
        /// Calculate the ratio of dark cells and give penalty if the ratio is far from 50%.
        /// It gives 10 penalty for 5% distance.
        /// </summary>
        /// <param name="matrix"></param>
        /// <returns></returns>
        private int _CalculatePenaltyByRule4(QMatrix matrix)
        {
            int count = 0;
            matrix.ForEach(
                new Action<int, int>((x, y) =>
                {   // Action is called for each cell, in order X++; Y++ :
                    if (matrix[x, y] == '1')
                        count++;
                }
                ));
            decimal ratio = (decimal)count / (decimal)(matrix.Width * matrix.Height);     // 0.00 = all is '0',  1.00 = all is '1'. Optimal = 0.50.
            return (int)(200m * Math.Abs(ratio - 0.5m));
        }
        #endregion
        #endregion
        #region TimeDiagnostic
        private void _InitTimeInfo()
        {
            this._TimeFrequency = (decimal)System.Diagnostics.Stopwatch.Frequency;
            this._TimeInfoList = new List<KeyValuePair<string, decimal>>();
            this._Timer = System.Diagnostics.Stopwatch.StartNew();
        }
        private void _AddTimeInfo(string name)
        {
            long ticks = _Timer.ElapsedTicks;
            decimal microsec = Math.Round((1000000m * (decimal)ticks / _TimeFrequency),2);
            _TimeInfoList.Add(new KeyValuePair<string, decimal>(name, microsec));
            _Timer.Restart();
        }
        private void _DisposeTimeInfo()
        {
            this._Timer.Stop();
            this._Timer = null;
            var times = this._TimeInfoList;
        }
        System.Diagnostics.Stopwatch _Timer;
        List<KeyValuePair<string, decimal>> _TimeInfoList;
        decimal _TimeFrequency;
        #endregion
    }
}
